/*
 * Copyright 2020-2025 the original author or authors.
 * You cannot use this file unless authorized by the author.
 */

package org.ipig.commons.helper;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.model.DeleteOptions;
import com.mongodb.client.model.ReplaceOptions;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.lang3.StringUtils;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.Decimal128;
import org.ipig.constants.DataCnst;
import org.ipig.constants.DbmsOperators;
import org.ipig.constants.MongoCnst;
import org.ipig.constants.SymbolCnst;
import org.ipig.data.mongo.MongoOperators;
import org.ipig.model.store.StoreFieldCnst;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * mongo 文档处理
 *
 * @author <a href="mailto:comchnts@163.com">chinats</a>
 * @version $$Id: MongoDocHelper.java 22 2020-05-20 03:47:46Z ts <comchnts@163.com> $$
 * @since 1.0
 */
@Slf4j
public final class MongoDocHelper {
    /**
     * 转换函数
     */
    public static final String FUNCTION_CONVERT = "convert";
    /**
     * 转换函数前缀
     */
    public static final String PREFIX_FUNCTION_CONVERT = FUNCTION_CONVERT + SymbolCnst.LEFT_PARENTHESIS;

    /**
     * 扩展字段构建【框架扩展字段】
     *
     * @param msgId      String
     * @param dataSource String
     * @param opTyte     DbmsOperators.OpType
     * @param jsonObj    JSONObject
     */
    public static void buildSkeletonFields(final String msgId, final String dataSource, final DbmsOperators.OpType opTyte, JSONObject jsonObj) {
        //数据存储框架扩展字段
        String dateTime = DateTimeHelper.getDateTime();
        jsonObj.put(StoreFieldCnst.SK_DELETED_FLAG, DataCnst.FLAG_NORMAL);
        jsonObj.put(StoreFieldCnst.SK_DATA_SOURCE, dataSource);
        jsonObj.put(StoreFieldCnst.SK_MSG_ID, msgId);
        jsonObj.put(StoreFieldCnst.SK_MODIFY_TIME, dateTime);
        jsonObj.put(StoreFieldCnst.SK_OP_TYPE, opTyte.code);
    }

    /**
     * 将原始值 转化成目标类型的对应的值
     *
     * @param targetType 目标类型
     * @param origValue  原始值
     * @return 转化成目标类型的对应的值
     */
    public static Object convert(final String targetType, final String origValue) {
        Object newRlatKeyValue = null;
        try {
            if (StringUtils.equalsIgnoreCase(targetType, "int")) {
                newRlatKeyValue = Integer.valueOf(origValue);
            } else if (StringUtils.equalsIgnoreCase(targetType, "long")) {
                newRlatKeyValue = Long.valueOf(origValue);
            } else if (StringUtils.equalsIgnoreCase(targetType, "float")) {
                newRlatKeyValue = Float.valueOf(origValue);
            } else if (StringUtils.equalsIgnoreCase(targetType, "double")) {
                newRlatKeyValue = Double.valueOf(origValue);
            } else if (StringUtils.equalsIgnoreCase(targetType, "string")) {
                newRlatKeyValue = String.valueOf(origValue);
            } else if (StringUtils.equalsIgnoreCase(targetType, "boolean")) {
                newRlatKeyValue = Boolean.valueOf(origValue);
            }
        } catch (Exception ex) {
            log.error(ExceptionUtils.getFullStackTrace(ex));
        }
        return newRlatKeyValue;
    }

    /**
     * createOrUpdateOne
     *
     * @param coll    MongoCollection<Document>
     * @param filter  Bson
     * @param jsonObj JSONObject
     */
    public static void createOrUpdateOne(MongoCollection<Document> coll, Bson filter, final JSONObject jsonObj) {
        boolean upsert = true;
        String newJson = JSONObject.toJSONString(jsonObj, SerializerFeature.SortField.MapSortField);
        coll.replaceOne(filter, Document.parse(newJson), new ReplaceOptions().upsert(upsert));
    }

    /**
     * createOrUpdate
     *
     * @param coll   MongoCollection<Document>
     * @param filter Bson
     * @param doc    Document
     */
    public static void createOrUpdateOne(MongoCollection<Document> coll, Bson filter, final Document doc) {
        boolean upsert = true;
        coll.replaceOne(filter, doc, new ReplaceOptions().upsert(upsert));
    }

    /**
     * createOne
     *
     * @param coll    MongoCollection<Document>
     * @param jsonObj JSONObject
     */
    public static void createOne(MongoCollection<Document> coll, final JSONObject jsonObj) {
        String newJson = JSONObject.toJSONString(jsonObj, SerializerFeature.SortField.MapSortField);
        coll.insertOne(Document.parse(newJson));
    }

    /**
     * createOne
     *
     * @param coll MongoCollection<Document>
     * @param doc  Document
     */
    public static void createOne(MongoCollection<Document> coll, final Document doc) {
        coll.insertOne(doc);
    }

    public static Number convertToNumber(Object value) {
        Number number = null;
        if (value instanceof Decimal128) {
            number = Decimal128.class.cast(value).bigDecimalValue();
        } else {
            number = Number.class.cast(value);
        }
        return number;
    }

    public static DeleteResult deleteOne(MongoCollection<Document> coll, Bson filter) {
        return coll.deleteOne(filter);
    }

    public static DeleteResult deleteOne(MongoCollection<Document> coll, Bson filter, DeleteOptions options) {
        return coll.deleteOne(filter, options);
    }

    public static String document2Json(Document doc) {
        return doc.toJson();
    }

    /**
     * find
     *
     * @param coll   MongoCollection<Document>
     * @param filter Bson
     * @return List<Document>
     */
    public static Document findOne(MongoCollection<Document> coll, Bson filter) {
        Document doc = null;
        List<Document> docList = find(coll, filter, 1, 1);
        if (docList != null && !docList.isEmpty()) {
            doc = docList.iterator().next();
        }
        return doc;
    }

    /**
     * find
     *
     * @param coll   MongoCollection<Document>
     * @param filter Bson
     * @param limit  int
     * @return List<Document>
     */
    public static List<Document> find(MongoCollection<Document> coll, Bson filter, int limit) {
        return find(coll, filter, 1000, limit);
    }

    /**
     * find
     *
     * @param coll      MongoCollection<Document>
     * @param filter    Bson
     * @param batchSize int
     * @param limit     int
     * @return List<Document>
     */
    public static List<Document> find(MongoCollection<Document> coll, Bson filter, int batchSize, int limit) {
        return find(coll, filter, null, null, batchSize, limit, 0);
    }

    /**
     * find
     *
     * @param coll       MongoCollection<Document>
     * @param filter     Bson
     * @param projection Bson
     * @param sort       Bson
     * @param limit      int
     * @param skip       int
     * @return List<Document>
     */
    public static List<Document> find(MongoCollection<Document> coll, Bson filter, Bson projection, Bson sort, int limit, int skip) {
        return find(coll, filter, projection, sort, 0, limit, skip);
    }

    /**
     * find
     *
     * @param coll       MongoCollection<Document>
     * @param filter     Bson
     * @param projection Bson
     * @param sort       Bson
     * @param batchSize  int
     * @param limit      int
     * @param skip       int
     * @return List<Document>
     */
    public static List<Document> find(MongoCollection<Document> coll, Bson filter, Bson projection, Bson sort,
                                      int batchSize, int limit, int skip) {
        List<Document> docList = new ArrayList();
        FindIterable<Document> findIterable = null;
        if (filter != null) {
            findIterable = coll.find(filter);
        } else {
            findIterable = coll.find();
        }
        if (projection != null) {
            findIterable = findIterable.projection(projection);
        }
        if (sort != null) {
            findIterable = findIterable.sort(sort);
        }
        if (batchSize > 0) {
            findIterable = findIterable.batchSize(batchSize);
        }
        if (limit > 0) {
            findIterable = findIterable.limit(limit);
        }
        if (skip > 0) {
            findIterable = findIterable.skip(skip);
        }
        MongoCursor<Document> mongoCursor = findIterable.iterator();
        try {
            while (mongoCursor.hasNext()) {
                docList.add(mongoCursor.next());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (mongoCursor != null) {
                mongoCursor.close();
            }
        }
        return docList;
    }

    /**
     * findToCursor
     * 特别提醒：使用完成之后必须要Cursor
     *
     * @param coll   MongoCollection<Document>
     * @param filter Bson
     * @return MongoCursor<Document>
     */
    public static MongoCursor<Document> findToCursor(MongoCollection<Document> coll, Bson filter) {
        FindIterable<Document> result = null;
        if (filter == null) {
            result = coll.find();
        } else {
            result = coll.find(filter);
        }
        return result.iterator();
    }

    /**
     * findToCursor
     * 特别提醒：使用完成之后必须要Cursor
     *
     * @param coll      MongoCollection<Document>
     * @param filter    Bson
     * @param batchSize int
     * @return MongoCursor<Document>
     */
    public static MongoCursor<Document> findToCursor(MongoCollection<Document> coll, Bson filter, int batchSize) {
        FindIterable<Document> result = null;
        if (filter == null) {
            result = coll.find().batchSize(batchSize);
        } else {
            result = coll.find(filter).batchSize(batchSize);
        }
        return result.iterator();
    }

    /**
     * 组装排序字段
     *
     * @param sorts 排序，多个以逗号隔开
     * @return Document
     */
    public static Document getSortDoc(String sorts) {
        Document sortDoc = new Document();
        String[] sortFieldArgs = StringUtils.split(sorts, SymbolCnst.COMMA);
        for (int i = 0; i < sortFieldArgs.length; i++) {
            String[] fieldKeys = StringUtils.split(sortFieldArgs[i], SymbolCnst.COLON);
            String sortField = fieldKeys[0];  //排序字段
            String sortType = fieldKeys[1];    // 排序顺序
            //排序一定要有索引
            sortDoc.append(sortField, DbmsOperators.SortType.parse(sortType).num);
        }
        if (sortDoc.isEmpty()) {
            sortDoc.append(MongoCnst.DEFAULT_ID, DbmsOperators.SortType.DESC.num);
        }
        return sortDoc;
    }


    /**
     * 组装条件字段
     *
     * @param filters 条件，多个以逗号隔开
     * @return Document
     */
    public static Document getFilterDoc(String filters) {
        //filter
        Document filterDoc = new Document();
        String[] fieldRlatArgs = StringUtils.split(filters, SymbolCnst.COMMA);
        for (int i = 0; i < fieldRlatArgs.length; i++) {

/*
            //查询价格200-9000范围的数据 $gt大于,$lt小于
            db.prodgory.find({"price":{$gt:"200",$lt:"9000"}})
            //给定范围查询 $in
            db.product1.find({"category":{$in:['手机','笔记本电脑','书籍']}})
            //给定范围查询取反   $nin
            db.product1.find({"category":{$nin:['手机','笔记本电脑','书籍']}})
*/


            String expression = StringUtils.trimToEmpty(fieldRlatArgs[i]);
            if (StringUtils.indexOf(expression, SymbolCnst.GREATER_THAN) > 0
                    && StringUtils.indexOf(expression, SymbolCnst.LESS_THAN) > 0) {
                String[] fieldKeys = StringUtils.split(expression, SymbolCnst.GREATER_THAN);
                String key = fieldKeys[0];//key
                String value = fieldKeys[1];//value
                filterDoc.append(key, value);


                filterDoc.append(key, new Document().append(MongoOperators.GT, value));
            }


            String[] fieldKeys = StringUtils.split(expression, SymbolCnst.EQUALS);
            if (fieldKeys.length > 0) {

            }

            String key = fieldKeys[0];//key
            String value = fieldKeys[1];//value
            if (StringUtils.startsWith(value, PREFIX_FUNCTION_CONVERT)) {
                value = StringUtils.removeStart(value, PREFIX_FUNCTION_CONVERT);
                value = StringUtils.removeEnd(value, SymbolCnst.RIGHT_PARENTHESIS);
                String[] keyValueConvertArgs = StringUtils.split(value, SymbolCnst.COLON);
                String targetType = keyValueConvertArgs[0];//目标类型
                String origValue = keyValueConvertArgs[1]; //原始值
                Object newRlatKeyValue = convert(targetType, origValue);
                filterDoc.append(key, newRlatKeyValue);
            } else {
                filterDoc.append(key, value);
            }
        }
        return filterDoc;
    }

    /**
     * 组装投影字段
     *
     * @param fields 投影字段，多个以逗号隔开
     * @return Document
     */
    public static Document getProjectDoc(String fields) {
        //project
        Document projectDoc = new Document().append(MongoCnst.DEFAULT_ID, 0);
        String[] fieldKeyArgs = StringUtils.split(fields, SymbolCnst.COMMA);
        for (int i = 0; i < fieldKeyArgs.length; i++) {
            projectDoc.append(fieldKeyArgs[i], 1);
        }
        return projectDoc;
    }

    /**
     * getValue
     *
     * @param doc Document
     * @param key String
     * @return String
     */
    public static String getValue(final Document doc, final String key) {
        return getValue(doc, key, SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue);
    }


    /**
     * getValue
     *
     * @param doc      Document
     * @param key      String
     * @param features SerializerFeature[]
     * @return String
     */
/*    public static String getValue(final Document doc, final String key, final SerializerFeature... features) {
        //SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue,SerializerFeature.WriteDateUseDateFormat
        String json = StringUtils.EMPTY;
        Object obj = doc.get(key);
        if (obj != null && obj instanceof Document) {
            if (features != null && features.length > 0) {
                json = JSONObject.toJSONString(obj, features);
            } else {
                json = ((Document) obj).toJson();
            }
        } else {
            json = obj == null ? StringUtils.EMPTY : obj.toString();
            if ((features != null && features.length > 0)
                    && JSONObject.isValidObject(json)) {
                json = JSONObject.parseObject(json).toString(features);
            }
        }
        return json;
    }*/


    /**
     * getValue
     *
     * @param doc      Document
     * @param key      String
     * @param features SerializerFeature[]
     * @return String
     */
    public static String getValue(final Document doc, final String key, final SerializerFeature... features) {
        String result = StringUtils.EMPTY;
        if (StringUtils.indexOf(key, SymbolCnst.DOT) > 0) {
            //key的格式：period.periodYear,remark
            String[] docKeyArgs = StringUtils.split(key, SymbolCnst.DOT);
            Object value = doc;
            for (int j = 0; j < docKeyArgs.length; j++) {
                if (value != null) {
                    if (value != null && value instanceof Document) {
                        value = ((Document) value).get(docKeyArgs[j]);
                    }
                } else {
                    break;
                }
            }
            result = value == null ? StringUtils.EMPTY : value.toString();
            if ((features != null && features.length > 0)
                    && JSONObject.isValidObject(result)) {
                result = JSONObject.parseObject(result).toString(features);
            }
        } else {
/*            Object docKeyValue = doc.get(key);
            result=docKeyValue == null ? StringUtils.EMPTY : docKeyValue.toString();*/
            //SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue,SerializerFeature.WriteDateUseDateFormat
            Object obj = doc.get(key);
            if (obj != null && obj instanceof Document) {
                if (features != null && features.length > 0) {
                    result = JSONObject.toJSONString(obj, features);
                } else {
                    result = ((Document) obj).toJson();
                }
            } else {
                result = obj == null ? StringUtils.EMPTY : obj.toString();
                if ((features != null && features.length > 0)
                        && JSONObject.isValidObject(result)) {
                    result = JSONObject.parseObject(result).toString(features);
                }
            }
        }
        return result;
    }

/*    public static String getValue2(final Document doc, final String docKey) {
        String result = StringUtils.EMPTY;
        if (StringUtils.indexOf(docKey, SymbolCnst.DOT) > 0) {
            //key的格式：period.periodYear,remark
            String[] docKeyArgs = StringUtils.split(docKey, SymbolCnst.DOT);
            Object value = doc;
            for (int j = 0; j < docKeyArgs.length; j++) {
                if (value != null) {
                    if (value != null && value instanceof Document) {
                        value = ((Document) value).get(docKeyArgs[j]);
                    }
                } else {
                    break;
                }
            }
            if (value != null) {
                result = value.toString();
            }
        } else {
            Object docKeyValue = doc.get(docKey);
            result = docKeyValue == null ? StringUtils.EMPTY : docKeyValue.toString();
        }
        return result;
    }*/

    /**
     * getMultValue
     *
     * @param doc            Document
     * @param keyList        List<String>
     * @param valueSeparator value分隔符
     * @return String 支持多个值以[valueSeparator]value分隔符分开
     */
    public static String getMultValue(final Document doc, final List<String> keyList, final String valueSeparator) {
        if (keyList == null || keyList.isEmpty()) {
            return StringUtils.EMPTY;
        }
        int length = keyList.size();
        StringBuilder valueSb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            String docKey = keyList.get(i);
            valueSb.append(getValue(doc, docKey, null));
            if (i < length - 1) {
                valueSb.append(valueSeparator);
            }
        }
        return valueSb.toString();
    }

    /**
     * getMultValue
     *
     * @param doc            Document
     * @param keyFieldArgs   String[]
     * @param valueSeparator value分隔符
     * @return String 支持多个值以[valueSeparator]value分隔符分开
     */
    public static String getMultValue(final Document doc, final String[] keyFieldArgs, final String valueSeparator) {
        if (keyFieldArgs == null || keyFieldArgs.length == 0) {
            return StringUtils.EMPTY;
        }
        int length = keyFieldArgs.length;
        StringBuilder valueSb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            String docKey = keyFieldArgs[i];
            valueSb.append(getValue(doc, docKey, null));
            if (i < length - 1) {
                valueSb.append(valueSeparator);
            }
        }
        return valueSb.toString();
    }

    /**
     * getMultValue
     *
     * @param doc            Document
     * @param keyFields      支持多个key,且以[keySeparator]key分隔符分开
     * @param keySeparator   key分隔符
     * @param valueSeparator value分隔符
     * @return String 支持多个值以[valueSeparator]value分隔符分开
     */
    public static String getMultValue(final Document doc, final String keyFields, final String keySeparator, final String valueSeparator) {
        if (StringUtils.isBlank(keyFields)) {
            return StringUtils.EMPTY;
        }
        String[] keyfieldArgs = StringUtils.split(keyFields, keySeparator);
        int length = keyfieldArgs.length;
        StringBuilder valueSb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            String docKey = keyfieldArgs[i];
            valueSb.append(getValue(doc, docKey, null));
            if (i < length - 1) {
                valueSb.append(valueSeparator);
            }
        }
        return valueSb.toString();
    }

    public static Document json2Document(String json) {
        return Document.parse(json);
    }

    /**
     * 合并Document对象,如果新的doc与原始的doc有相同的key，全新后新的doc会覆盖原始doc
     *
     * @param origDoc 原始Document
     * @param newDoc  新的Document
     * @return 合并后的Document
     */
    public static Document mergeDoc(Document origDoc, Document newDoc) {
        Document mergeDoc = new Document();
        mergeDoc.putAll(origDoc);
        mergeDoc.putAll(newDoc);
        return mergeDoc;
    }


    /**
     * queryData
     *
     * @param coll MongoCollection<Document>
     * @return List<Document>
     */
/*    public static List<Document> queryData(MongoCollection<Document> coll) {
        List<Document> docList = new ArrayList();
        FindIterable<Document> result = coll.find();
        MongoCursor<Document> mongoCursor = result.iterator();
        try {
            while (mongoCursor.hasNext()) {
                Document doc = mongoCursor.next();
                docList.add(doc);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error(ExceptionUtils.getFullStackTrace(e));
        } finally {
            if (mongoCursor != null) {
                mongoCursor.close();
            }
        }
        return docList;
    }*/


    /**
     * 扩展字段移除【框架扩展字段】
     *
     * @param map Map<String, Object>
     */
    public static void removeSkeletonFields(Map<String, Object> map) {
        if (map != null) {
            map.remove(StoreFieldCnst.SK_DELETED_FLAG);
            map.remove(StoreFieldCnst.SK_DATA_SOURCE);
            map.remove(StoreFieldCnst.SK_MSG_ID);
            map.remove(StoreFieldCnst.SK_MODIFY_TIME);
            map.remove(StoreFieldCnst.SK_OP_TYPE);
        }
    }

    /**
     * updateOne
     *
     * @param coll   MongoCollection<Document>
     * @param filter Bson
     * @param update Map<String, Object>
     * @return UpdateResult
     */
    public static UpdateResult updateOne(MongoCollection<Document> coll, Bson filter, final Map<String, Object> update) {
        if (coll == null || filter == null || update == null || update.isEmpty()) {
            return null;
        }
        return coll.updateOne(filter, new Document(MongoOperators.SET, update));
    }
}